package com.rbac.application.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.rbac.application.service.RoleService;
import com.rbac.common.enums.BusinessErrorCode;
import com.rbac.common.exception.BusinessException;
import com.rbac.domain.dto.query.RolePageQuery;
import com.rbac.domain.dto.request.RoleCreateRequest;
import com.rbac.domain.dto.request.RoleUpdateRequest;
import com.rbac.domain.entity.Role;
import com.rbac.domain.entity.RolePermission;
import com.rbac.infrastructure.mapper.RoleMapper;
import com.rbac.infrastructure.mapper.RolePermissionMapper;
import com.rbac.infrastructure.mapper.UserRoleMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.List;

@Service
@RequiredArgsConstructor
public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements RoleService {

    private final RolePermissionMapper rolePermissionMapper;
    private final UserRoleMapper userRoleMapper;

    @Override
    public IPage<Role> pageRoles(RolePageQuery query) {
        Page<Role> page = new Page<>(query.getPage(), query.getSize());
        LambdaQueryWrapper<Role> wrapper = new LambdaQueryWrapper<>();
        wrapper.like(StrUtil.isNotBlank(query.getRoleName()), Role::getRoleName, query.getRoleName())
                .like(StrUtil.isNotBlank(query.getRoleCode()), Role::getRoleCode, query.getRoleCode())
                .eq(query.getStatus() != null, Role::getStatus, query.getStatus())
                .orderByAsc(Role::getSortOrder)
                .orderByDesc(Role::getCreatedTime);
        return this.page(page, wrapper);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Role createRole(RoleCreateRequest request) {
        if (this.checkRoleCodeExists(request.getRoleCode())) {
            throw new BusinessException(BusinessErrorCode.ROLE_ALREADY_EXISTS);
        }

        Role role = new Role();
        role.setRoleName(request.getRoleName());
        role.setRoleCode(request.getRoleCode());
        role.setSortOrder(request.getSortOrder());
        role.setStatus(request.getStatus());
        role.setRemark(request.getRemark());
        role.setCreatedTime(LocalDateTime.now());

        this.save(role);

        if (CollUtil.isNotEmpty(request.getPermissionIds())) {
            this.assignPermissions(role.getId(), request.getPermissionIds());
        }

        return role;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Role updateRole(RoleUpdateRequest request) {
        Role role = this.getById(request.getId());
        if (role == null) {
            throw new BusinessException(BusinessErrorCode.ROLE_NOT_FOUND);
        }

        if (role.getId().equals(1L)) {
            throw new BusinessException(BusinessErrorCode.PERMISSION_DENIED);
        }

        role.setRoleName(request.getRoleName());
        role.setSortOrder(request.getSortOrder());
        role.setStatus(request.getStatus());
        role.setRemark(request.getRemark());
        role.setUpdatedTime(LocalDateTime.now());

        this.updateById(role);

        if (request.getPermissionIds() != null) {
            this.assignPermissions(role.getId(), request.getPermissionIds());
        }

        return role;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteRole(Long id) {
        if (id.equals(1L)) {
            throw new BusinessException(BusinessErrorCode.PERMISSION_DENIED);
        }
        
        this.removeById(id);
        rolePermissionMapper.deleteByRoleId(id);
        userRoleMapper.deleteByRoleId(id);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteBatchRoles(List<Long> ids) {
        if (ids.contains(1L)) {
            throw new BusinessException(BusinessErrorCode.PERMISSION_DENIED);
        }
        
        this.removeByIds(ids);
        rolePermissionMapper.deleteByRoleIds(ids);
        ids.forEach(userRoleMapper::deleteByRoleId);
    }

    @Override
    public Role getRoleWithPermissions(Long id) {
        return baseMapper.getRoleWithPermissions(id);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void assignPermissions(Long roleId, List<Long> permissionIds) {
        rolePermissionMapper.deleteByRoleId(roleId);

        if (CollUtil.isNotEmpty(permissionIds)) {
            List<RolePermission> rolePermissions = permissionIds.stream()
                    .map(permissionId -> {
                        RolePermission rolePermission = new RolePermission();
                        rolePermission.setRoleId(roleId);
                        rolePermission.setPermissionId(permissionId);
                        rolePermission.setCreatedTime(LocalDateTime.now());
                        return rolePermission;
                    })
                    .toList();
            rolePermissionMapper.insertBatch(rolePermissions);
        }
    }

    @Override
    public List<Role> getRolesByUserId(Long userId) {
        return baseMapper.getRolesByUserId(userId);
    }

    @Override
    public List<Role> getAllEnabledRoles() {
        LambdaQueryWrapper<Role> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Role::getStatus, 1)
                .orderByAsc(Role::getSortOrder);
        return this.list(wrapper);
    }

    private boolean checkRoleCodeExists(String roleCode) {
        LambdaQueryWrapper<Role> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Role::getRoleCode, roleCode);
        return this.count(wrapper) > 0;
    }
}